<?php
abstract class BaseMapperOld
{
	protected $factory;
	
	protected $className;
	protected $reflectionClassMapper;
    /**
     * @var DomainObjectAbstract
     */
	protected $modelObject;
	protected $classDefinitions;
	
	protected $tableName;
	protected $sortField;
	protected $sortOrder = 'ASC';
	
	protected $key;
	
	protected $dateTimeFields = array();
	protected $booleanFields = array();

	public function __construct()
	{
		$this->reflectionClassMapper = new ReflectionClass($this->className);
		$this->modelObject = $this->reflectionClassMapper->newInstance();
	}
	
	public function getTableName()
	{
		return $this->tableName;
	}

	public function getIndexKeyField()
	{
		return $this->key;
	}
	
	public function getSQLKeyField()
	{
		return $this->tableName.".".$this->key;
	}
	
	public function setSortField($sortField)
	{
		$this->sortField = $sortField;
	}
	
	public function getSortField()
	{
		return $this->sortField;
	}

	public function setSortOrder($sortOrder)
	{
		$this->sortOrder = $sortOrder;
	}
	
	public function setDateTimeFields($dateTimeFields)
	{
		$this->dateTimeFields = $dateTimeFields;
	}

	public function setBooleanFields($booleanFields)
	{
		$this->booleanFields = $booleanFields;
	}

	public function lockTable()
	{
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$query_lock = "LOCK TABLES ".$this->tableName." WRITE, ".$this->tableName." AS ".$this->tableName."_for_read READ";		
		return $db->query($query_lock);
	}
	
	public function unLockTable()
	{
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$query_unlock = "UNLOCK TABLES";		
		return $db->query($query_unlock);
	}
	
	protected function getFirstRowFromQuery($query)
	{
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$query_id = $db->query($query);

		if ($record = $db->fetch($query_id))
		{
			$row = $this->getRowFromRecord($record);
			$this->setDateTimeFieldsFromUTCToLocale($row);
			$this->mapBooleanFields($row);
			$this->getSubClassesFields($row);
			return $row;
		}
		return null;
	}
	
	protected function getRowFromRecord( $record )
	{
		return $this->reflectionClassMapper->newInstance( $record );
	}
	
	/**
	 * @param SearchQuery $searchQuery
	 * @return int
	 */
	public function getCountFromQuery($searchQuery)
	{
		$query = $searchQuery->getSQLQuery();
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$query_id = $db->query($query);
		$record = $db->fetch($query_id);
		return $record->QtyRows;
	}

	/**
	 * @param QueryObject $queryObject
	 */
	public function getAllRowsFromQueryObject( $queryObject )
	{
		return $this->getAllRowsFromQuery( $queryObject->getSql() );
	}
	
	protected function getAllRowsFromQuery($query)
	{
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$query_id = $db->query($query);

		$result = array();
		$lastIdValue = 0;
		while ($record = $db->fetch($query_id))
		{
			// pour eviter les doublons lorsque la requete a des jointures
			if ($record->{$this->key} != $lastIdValue)
			{
				$row = $this->getRowFromRecord( $record );
				$this->setDateTimeFieldsFromUTCToLocale($row);
				$this->mapBooleanFields($row);
				$this->getSubClassesFields($row);
				$result[] = $row;
				$lastIdValue = $row->{$this->key};
			}
		}
		return $result;
	}
	
	protected function runQuery($query)
	{
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		return $db->query($query);
	}

	protected function getSubqueryIDs($query, $IDColName)
	{
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$db->query($query);
		$allRows = $db->fetchAll();
		$ids = array('0');
		foreach($allRows as $row)
		{
			$ids[] = $row->{$IDColName};
		}
		return join(',', $ids);
	}
	
	public function getById($id)
	{
		if ($id > 0)
		{
			$query = "SELECT * FROM ".$this->tableName." WHERE ".$this->key."='".$id."' LIMIT 1";

			return $this->getFirstRowFromQuery($query);
		}
	}

	public function getByIds($ids)
	{
		if (count($ids)>0)
		{
			$query = "SELECT * FROM ".$this->tableName." WHERE ".$this->key." IN (".join(',',$ids).")";

			return $this->getAllRowsFromQuery($query);
		}
	}

	public function getAll()
	{
		$query = "SELECT * FROM ".$this->tableName." ORDER BY ".$this->sortField." ".$this->sortOrder;
		
		return $this->getAllRowsFromQuery($query);
	}

	public function deleteById($id)
	{
		if ($id > 0)
		{
			$query = "DELETE FROM ".$this->tableName." WHERE ".$this->key."='".$id."'";
			
			return $this->runQuery($query);
		}
	}
	
	public function deleteAll()
	{
		$query = "TRUNCATE TABLE ".$this->tableName;
		return $this->runQuery($query);
	}
	
	public function deleteByCriterias($criterias)
	{
		$queryObject = new QueryObject;
		$queryObject->delete()->from($this->tableName);
		
		if (!is_array($criterias)) {$criterias = array($criterias);}
		foreach ($criterias as $criteria)
		{
			$queryObject->addCriteria($criteria);
		}		
		$query = $queryObject->getSql();
		if (strpos($query, "WHERE") > 0)
		{
			return $this->runQuery($query);
		}
	}
	
	public function save(&$entity)
	{
		if( $entity != null )
		{
			if ($this->exists($entity))
			{
				$this->update($entity);
			}
			else
			{
				$this->insert($entity);
			}
			$this->saveSubClassesFields($entity);
		}
		return $entity;
	}
	
	public function exists($entity)
	{
		$idField = $this->modelObject->getIdField();
		$ID = isset($entity->{$idField}) ? $entity->{$idField} : 0;
		return ($ID > 0);
	}
	
	public function insert(&$entity)
	{
		$this->setDateTimeFieldsFromLocaleToUTC($entity);
		
		$entityToInsert = $this->getEntityWithOnlyDbFields($entity);
		$entityToInsert->{$this->modelObject->getIdField()} = "";
		
		$query = "INSERT INTO ".$this->tableName." (";
		foreach ($entityToInsert as $field => $value)
		{
			$query.= '`'.$field."`,";
		}
		$query = substr($query, 0, -1);
		$query.= ") VALUES (";
		foreach ($entityToInsert as $field => $value)
		{
			$query.= "'".magic_quote($value)."',";
		}
		$query = substr($query, 0, -1);
		$query.= ")";
		
		$this->setDateTimeFieldsFromUTCToLocale($entity);
		
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$db->query($query);
		
		$entity->{$this->modelObject->getIdField()} = $db->getInsertID();
		return $entity->{$this->modelObject->getIdField()};
	}
	
	protected function update(&$entity)
	{
		$this->setDateTimeFieldsFromLocaleToUTC($entity);
		
		$entityToUpdate = $this->getEntityWithOnlyDbFields($entity);
		
		$query = "UPDATE ".$this->tableName." SET ";
		foreach ($entityToUpdate as $field => $value)
		{
			$query.= "`".$field."`='".magic_quote($entity->{$field})."',";
		}
		$query = substr($query, 0, -1);
		$query.= " WHERE `".$this->key."`='".$entity->{$this->key}."'";
		$this->runQuery($query);
		
		$this->setDateTimeFieldsFromUTCToLocale($entity);
		
		return $entity->{$this->modelObject->getIdField()};
	}	
	
	/**
	 * @param Criteria[]
	 */
	public function findByCriterias($criterias = array(), $order = '')
	{
		$queryObject = $this->getQueryObjectFromCriterias( $criterias, $order );

		return $this->getAllRowsFromQueryObject( $queryObject );
	}
	
	protected function getQueryObjectFromCriterias( $criterias, $order = '' )
	{
		if (!is_array($criterias)) {$criterias = array($criterias);}

		$queryObject = new QueryObject;
		$queryObject->select()->from($this->tableName);
		$queryObject->orderBy( $order != '' ? $order : $this->getSortField()." ".$this->sortOrder);
		
		foreach ($criterias as $criteria)
		{
			foreach($this->getJoinInformations($criteria) as $joinRule)
			{
				$queryObject->leftJoin($joinRule->getJoinTable(), $joinRule->getSqlRule());
			}
			$criteria = $this->getCriteriaWithTableName($criteria);
			$queryObject->addCriteria($criteria);
		}
		
		return $queryObject;
	}

    /**
     * @param Criteria $criteria
     * @return QueryJoinRule[]
     */
	protected function getJoinInformations($criteria)
	{
        $joinRules = array();
        foreach ($criteria->getAllSubcriterias() as $subcriteria)
        {
            if ($joinClassName = $subcriteria->getFieldClassName())
            {
                if ($joinClassName != $this->className)
                {
                    if ($mapper = ServiceLocator::getInstance()->getMapperByClassName($joinClassName))
                    {
                        foreach ($this->modelObject->getBelongsTo() as $relationship)
                        {
                            if ($relationship["className"] == $joinClassName)
                            {
                                $foreignKey = $relationship["foreignKey"];
                                $joinRule = new QueryJoinRule($mapper->getTableName(), $foreignKey, $this->getTableName(), $foreignKey);
                                $joinRules[] = $joinRule;
                            }
                        }
                        foreach ($this->modelObject->getHasMany() as $relationship)
                        {
                            if ($relationship["className"] == $joinClassName)
                            {
                                $foreignKey = $relationship["foreignKey"];
                                $joinRule = new QueryJoinRule($mapper->getTableName(), $foreignKey, $this->getTableName(), $foreignKey);
                                $joinRules[] = $joinRule;
                            }
                        }
                    }
                }
            }
        }
        return $joinRules;
	}
	
	/**
     * @param Criteria $criteria
	 * @return Criteria
	 */
	protected function getCriteriaWithTableName($criteria)
	{
		if ($className = $criteria->getFieldClassName())
		{
			if ($mapper = ServiceLocator::getInstance()->getMapperByClassName($className))
			{
				$tableName = $mapper->getTableName();
				$fieldName = substr($criteria->getFieldName(), strpos($criteria->getFieldName(), "::") + 2);
				$criteria->setFieldName($tableName.".".$fieldName);
			}
		}
		return $criteria;		
	}
	
	/**
	 * @param Criteria[]
	 */
	public function countByCriterias($criterias)
	{
		$queryObject = new QueryObject;
		$queryObject->select("COUNT(".$this->key.") AS quantity")->from($this->tableName);
		
		if (!is_array($criterias)) {$criterias = array($criterias);}
		foreach ($criterias as $criteria)
		{
			$queryObject->addCriteria($criteria);
		}		
		
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$query_id = $db->query($queryObject->getSql());
		
		if ($row = $db->fetch($query_id))
		{
			return $row->quantity;
		}
	}

	public function updateFieldForEntity( $fieldName, $fieldValue, $entityID )
	{
		$queryObject = new QueryObject;
		$queryObject->update($this->tableName);
		$queryObject->set( array( $fieldName => $fieldValue ) );
		$queryObject->addCriteria( new Criteria($this->key, Criteria::EQUAL, $entityID) );
		return $this->runQuery($queryObject->getSql());
	}

    /**
     * @param int $ID
     * @param string[] $fieldNames
     * @return stdClass
     */
    public function getFieldsValuesById($ID, $fieldNames)
    {
        $criteria = new Criteria($this->getIndexKeyField(), Criteria::EQUAL, $ID);
        $queryObject = new QueryObject;
        $queryObject->select($fieldNames)->from($this->getTableName())->where($criteria);

        $db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
        $query_id = $db->query($queryObject->getSql());
        if ($query_id)
        {
            return $db->fetch($query_id);
        }
        return null;
    }

	public function updateByCriterias($entityModel, $criterias)
	{
		$queryObject = new QueryObject;
		$queryObject->update($this->tableName);
		$queryObject->set($this->getEntityWithOnlyDbFields($entityModel));
		
		if (!is_array($criterias)) {$criterias = array($criterias);}
		foreach ($criterias as $criteria)
		{
			$queryObject->addCriteria($criteria);
		}
		
		return $this->runQuery($queryObject->getSql());
	}
	
	/**
	 * @param SearchQuery $searchQuery
	 */
	public function searchFromQuery( $searchQuery )
	{
		return $this->getAllRowsFromQuery($searchQuery->getSQLQuery());
	}
	
	/**
	 * @param SearchQuery $searchQuery
	 */
	public function getFoundRows( $searchQuery )
	{
		$db = MySQLAdapter::getInstance(Application::getInstance()->getDBConfig());
		$queryId = $db->query( $searchQuery->getSQLQueryCountRows( $this->getSQLKeyField() ) );
		$result = $db->fetch($queryId);
		return $result->QtyRows;
	}
  	
	protected function getSubClassesFields(&$row)
	{
		if (isset($row->{$this->key}) && $row->{$this->key} > 0) 
		{
			$hasMany = $this->modelObject->getHasMany();

			foreach($hasMany as $attributeName => $relation)
			{
				if( property_exists($row, $attributeName) )
				{
					$classDefinitions = $this->getClassDefinitions();
					$attributeDef = $classDefinitions[$attributeName];
					$type = (strpos($attributeDef["Type"], "[]") !== false) ? substr($attributeDef["Type"], 0, -2) : $attributeDef["Type"];
					if ($relation["className"] == $type)
					{
						$mapper = ServiceLocator::getInstance()->getMapperByClassName($type);
						if ($mapper)
						{
							$foreignKey = $hasMany[$attributeName]["foreignKey"];
							$criteria = new Criteria($foreignKey, Criteria::EQUAL, $row->{$foreignKey});
							$order = (isset($hasMany[$attributeName]["order"]) && $hasMany[$attributeName]["order"] != '') ? $hasMany[$attributeName]["order"] : "";
							$values = $mapper->findByCriterias($criteria, $order);
							if (strpos($attributeDef["Type"], "[]") === false)
							{
								$row->__set($attributeName, array_shift($values));
							}
							else
							{
								$row->__set($attributeName, $values);
							}
						}
					}
				}
			}
		}
		return $row;
	}
	
	protected function saveSubClassesFields(&$row)
	{
		if (isset($row->{$this->key}) && $row->{$this->key} > 0) 
		{
			$hasMany = $this->modelObject->getHasMany();

			foreach($hasMany as $attributeName => $relation)
			{
				if(!isset($hasMany[$attributeName]["doNotInsertOrUpdateFromParent"]))
				{
					if( property_exists($row, $attributeName) )
					{
						$classDefinitions = $this->getClassDefinitions();
						$attributeDef = $classDefinitions[$attributeName];
						$type = (strpos($attributeDef["Type"], "[]") !== false) ? substr($attributeDef["Type"], 0, -2) : $attributeDef["Type"];
						if ($relation["className"] == $type)
						{
							$mapper = ServiceLocator::getInstance()->getMapperByClassName($type);
							if ($mapper)
							{
								$foreignKey = $hasMany[$attributeName]["foreignKey"];
								$criteriaForEntitiesInDB = new Criteria($foreignKey, Criteria::EQUAL, $row->{$foreignKey});
								$subEntitiesInDB = $mapper->findByCriterias($criteriaForEntitiesInDB);

								if (strpos($attributeDef["Type"], "[]") === false)
								{
									$subEntityInDB = array_shift($subEntitiesInDB);
									if ($row->{$attributeName}->{$mapper->key} != $subEntityInDB->{$mapper->key})
									{
										$mapper->deleteById($subEntityInDB->{$mapper->key});
									}
									$row->{$attributeName}->{$foreignKey} = $row->{$foreignKey};
									$mapper->save($row->{$attributeName});
								}
								else
								{
									$keys = array();
									$subEntityList = isset($row->{$attributeName}) ? $row->{$attributeName} : array();
									foreach($subEntityList as &$subEntity)
									{
										$subEntity->{$foreignKey} = $row->{$foreignKey};
										$mapper->save($subEntity);
										$keys[] = $subEntity->{$mapper->key};
									}
									foreach ($subEntitiesInDB as $subEntityInDB)
									{
										if (!in_array($subEntityInDB->{$mapper->key}, $keys))
										{
											$mapper->deleteById($subEntityInDB->{$mapper->key});
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return $row;
	}
	
	protected function getEntityWithOnlyDbFields($entity)
	{
        $resultEntity = new stdClass();
		$attributeDefinitions = $this->getClassDefinitions();
		foreach ($entity as $field => $value)
		{
			if( $field != "_explicitType" )
			{
				$isDatabaseField = isset($attributeDefinitions[$field]) ? $attributeDefinitions[$field]["DbType"] === true : false;
				$isNotTransient = isset($attributeDefinitions[$field]["Transient"]) ? $attributeDefinitions[$field]["Transient"] !== true : true;

				if( $isNotTransient && $isDatabaseField)
				{
					$resultEntity->{$field} = $value;
				}
			}
		}
		return $resultEntity;
	}
	
	protected function getClassDefinitions()
	{
		if (!isset($this->classDefinitions))
		{
			$this->classDefinitions = Application::getInstance()->getClassAttributesFromParsedComments($this->className);
		}
		return $this->classDefinitions;
	}
	
	public function setDateTimeFieldsFromUTCToLocale(&$row)
	{
		if( $row )
		{
			foreach($this->dateTimeFields as $fieldName)
			{
				$row->{$fieldName} = getLocaleDateTimeFromUTC($row->{$fieldName});
			}
		}
		return $row;
	}

	public function mapBooleanFields(&$row)
	{
		foreach($this->booleanFields as $fieldName)
		{
			$row->{$fieldName} = (bool) $row->{$fieldName};
		}
		return $row;
	}

	protected function setDateTimeFieldsFromLocaleToUTC(&$row)
	{
		foreach($this->dateTimeFields as $fieldName)
		{
			$row->{$fieldName} = getUTCDateTimeFromLocale($row->{$fieldName});
		}
		return $row;		
	}
	
	protected function setDateTimeFieldsFromUTCToLocaleForRecordSet(&$rowSet)
	{
		foreach ($rowSet as &$row)
		{
			$this->setDateTimeFieldsFromUTCToLocale($row);
		}
		return $rowSet;		
	}
	
	protected function setDateTimeFieldsFromLocaleToUTCForRecordSet(&$rowSet)
	{
		foreach ($rowSet as &$row)
		{
			$this->setDateTimeFieldsFromLocaleToUTC($row);
		}
		return $rowSet;		
	}	
	
	public function synchronizeDatabase()
	{
		$synchronizer = new DBSynchronizer();
		$synchronizer->synchronizeDatabase($this->tableName, $this->getClassDefinitions(), $this->modelObject);
	}  		
}

?>